/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.camunda.bpm.container.impl.jboss.extension.handler;

import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.ALLOW_CORE_TIMEOUT;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.CORE_THREADS;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.JOB_EXECUTOR_ATTRIBUTES;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.KEEPALIVE_TIME;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.MAX_THREADS;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.QUEUE_LENGTH;
import static org.camunda.bpm.container.impl.jboss.extension.SubsystemAttributeDefinitons.THREAD_POOL_NAME;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.camunda.bpm.container.ExecutorService;
import org.camunda.bpm.container.impl.jboss.service.MscExecutorService;
import org.camunda.bpm.container.impl.jboss.service.ServiceNames;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.threads.ThreadFactoryService;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.threads.EnhancedQueueExecutor;
import org.jboss.threads.EnhancedQueueExecutor.Builder;

/**
 * Installs the JobExecutor service into the container.
 *
 * @author Christian Lipphardt
 */
public class JobExecutorAdd extends AbstractAddStepHandler {

  public static final String THREAD_POOL_GRP_NAME = "Camunda BPM ";

  public static final JobExecutorAdd INSTANCE = new JobExecutorAdd();

  protected JobExecutorAdd() {
    super(JOB_EXECUTOR_ATTRIBUTES);
  }

  @Override
  protected void performRuntime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {

    String jobExecutorThreadPoolName = THREAD_POOL_NAME.resolveModelAttribute(context, model).asString();
    ServiceName jobExecutorThreadPoolServiceName = ServiceNames.forManagedThreadPool(jobExecutorThreadPoolName);

    performRuntimeThreadPool(context, model, jobExecutorThreadPoolName, jobExecutorThreadPoolServiceName);

    ServiceName serviceName = ServiceNames.forMscExecutorService();
    ServiceBuilder<?> builder = context.getServiceTarget().addService(serviceName);
    Consumer<ExecutorService> provider = builder.provides(serviceName);
    Supplier<EnhancedQueueExecutor> supplier = builder.requires(jobExecutorThreadPoolServiceName);
    MscExecutorService service = new MscExecutorService(supplier, provider);
    builder.setInitialMode(ServiceController.Mode.ACTIVE);
    builder.setInstance(service);
    builder.install();
  }

  protected void performRuntimeThreadPool(OperationContext context,
                                          ModelNode model,
                                          String name,
                                          ServiceName jobExecutorThreadPoolServiceName)
      throws OperationFailedException {

    ServiceTarget serviceTarget = context.getServiceTarget();

    ThreadFactoryService threadFactory = new ThreadFactoryService();
    threadFactory.setThreadGroupName(THREAD_POOL_GRP_NAME + name);

    ServiceName threadFactoryServiceName = ServiceNames.forThreadFactoryService(name);

    serviceTarget.addService(threadFactoryServiceName, threadFactory).install();

    Builder executorBuilder = new Builder()
        .setMaximumPoolSize(MAX_THREADS.resolveModelAttribute(context, model).asInt())
        .setCorePoolSize(CORE_THREADS.resolveModelAttribute(context, model).asInt())
        .setMaximumQueueSize(QUEUE_LENGTH.resolveModelAttribute(context, model).asInt())
        .setKeepAliveTime(KEEPALIVE_TIME.resolveModelAttribute(context, model).asInt(), TimeUnit.SECONDS)
        .allowCoreThreadTimeOut(ALLOW_CORE_TIMEOUT.resolveModelAttribute(context, model).asBoolean());

    ServiceBuilder<EnhancedQueueExecutor> queueExecutorBuilder = serviceTarget.addService(jobExecutorThreadPoolServiceName, new EnhancedQueueExecutorService(executorBuilder));
    queueExecutorBuilder.requires(threadFactoryServiceName);
    queueExecutorBuilder.setInitialMode(ServiceController.Mode.ACTIVE).install();
  }

}
